package main

import (
	"fmt"
	"math/rand"
	"os"
	"snake/Clib"
	"strconv"
	"time"
)

// 全局常量
const (
	WIDTH     = 40  // 地图宽度
	HEIGHT    = 20  // 地图高度
	LENGTH    = 2   // 蛇长度
	DIRECTION = 'R' // 方向
)

// 全局变量
var (
	food  Food                            // 食物
	delay time.Duration = time.Second / 2 // 速度
	score int           = 0               // 分数
)

// 初始化父类坐标
type Position struct {
	X int
	Y int
}

// 初始化蛇子类
type Snake struct {
	len int                      // 长度
	dir int                      // 方向
	pos [WIDTH * HEIGHT]Position // 定义数组，存储每一节蛇的坐标
}

// 初始化食物子类
type Food struct {
	Position
}

// 初始化游戏地图
func MapInit() {
	fmt.Fprintln(os.Stderr, `
                    贪吃蛇
  #----------------------------------------#
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  |                                        |
  #----------------------------------------#
`)
}

// 初始化蛇信息
func (s *Snake) SnakeInit() {
	s.len = LENGTH    // 蛇长度
	s.dir = DIRECTION // 蛇方向

	// 蛇位置
	s.pos[0].X = WIDTH / 2
	s.pos[0].Y = HEIGHT / 2
	s.pos[1].X = WIDTH/2 - 1
	s.pos[1].Y = HEIGHT / 2

	s.snake()    // 绘制蛇
	s.getInput() // 接受键盘输入
}

// 绘制蛇
func (s *Snake) snake() {
	for i := 0; i < s.len; i++ {
		var ch byte
		// 区分蛇头和身体
		if i == 0 {
			ch = '@'
		} else {
			ch = '*'
		}
		Show(s.pos[i].X, s.pos[i].Y, ch)
	}
}

// 接受指定键盘按键输入
func (s *Snake) getInput() {
	go func() {
		for {
			switch Clib.Direction() {
			case 80, 68, 100:
				if s.dir != 'U' {
					s.dir = 'D'
				}
			case 72, 85, 117:
				if s.dir != 'D' {
					s.dir = 'U'
				}
			case 77, 82, 114:
				if s.dir != 'L' {
					s.dir = 'R'
				}
			case 75, 76, 108:
				if s.dir != 'R' {
					s.dir = 'L'
				}
			case 32:
				s.dir = 'P'
			}
		}
	}()
}

// 蛇运动
func (s *Snake) Play() {
	// 游戏流程控制
	for {
	FLAG:
		time.Sleep(delay) // 延迟调用
		if s.dir == 'P' {
			goto FLAG
		}
		if s.check() == false { // 生命检测
			break
		}
		s.eating()      // 享受美食
		s.changeSpeed() // 改变速度
		s.updatePos()   // 更新蛇位置
		s.snake()       // 绘制蛇的UI
	}
}

// 生命检测
func (s *Snake) check() bool {
	// 蛇和墙碰撞
	if s.pos[0].X <= 2 || s.pos[0].X >= WIDTH+3 || s.pos[0].Y <= 2 || s.pos[0].Y >= HEIGHT+3 {
		return false
	}
	// 蛇头和身体碰撞
	for i := 1; i < s.len; i++ {
		if s.pos[0].X == s.pos[i].X && s.pos[0].Y == s.pos[i].Y {
			return false
		}
	}
	return true
}

// 更新位置
func (s *Snake) updatePos() {
	Show(s.pos[s.len-1].X, s.pos[s.len-1].Y, ' ') // 将末尾置空
	// 更新蛇身体坐标
	for i := s.len - 1; i > 0; i-- {
		s.pos[i].X = s.pos[i-1].X
		s.pos[i].Y = s.pos[i-1].Y
		Show(s.pos[i].X, s.pos[i].Y, '*')
	}

	// 根据方向改变蛇头位置
	switch s.dir {
	case 'U':
		s.pos[0].Y += -1
	case 'D':
		s.pos[0].Y += 1
	case 'L':
		s.pos[0].X += -1
	case 'R':
		s.pos[0].X += 1
	}
	Show(s.pos[0].X, s.pos[0].Y, '@') // 绘制蛇头

}

// 享受美食
func (s *Snake) eating() {
	if s.pos[0].X == food.X && s.pos[0].Y == food.Y {
		s.len++        // 身体增加
		s.randomFood() // 创建随机食物
		score ++
		s.showScore() // 显示等分
	}
}

// 根据得分改变速度
func (s *Snake) changeSpeed() {
	delay = time.Second / (time.Duration(score/20) + 3)
}

// 随机食物
func (s *Snake) randomFood() {
	// 随机食物
	for i := 1; i < s.len; i++ {
		food.X = RandInt(3, WIDTH+3)
		food.Y = RandInt(3, HEIGHT+3)
		if food.X == s.pos[i].X && food.Y == s.pos[i].Y {
			continue
		}
	}
	Show(food.X, food.Y, '$') // 显示食物
}

// 显示信息
func Show(X int, Y int, ch byte) {
	Clib.GotoPosition(X, Y)          // 调用C语言代码设置控制光标位置
	fmt.Fprintf(os.Stderr, "%c", ch) // 将字符绘制在界面中
}

// 两者之间随机数 左闭右开
func RandInt(min, max int) int {
	if min >= max || min == 0 || max == 0 {
		return max
	}
	return rand.Intn(max-min) + min
}

// 显示等分
func (s *Snake) showScore() {
	Clib.GotoPosition(2, 24)
	fmt.Fprintf(os.Stderr, "%s", "分数："+strconv.Itoa(score))
}

// 游戏结束
func (s *Snake) gameOver() {
	// 擦除食物
	Clib.GotoPosition(food.X, food.Y)
	fmt.Fprintf(os.Stderr, "%s", " ")
	// 显示Game Over
	Clib.GotoPosition(18, 10)
	fmt.Fprintf(os.Stderr, "%s", "Game Over")
	// 显示等分
	Clib.GotoPosition(22, 12)
	fmt.Fprintf(os.Stderr, "%s", strconv.Itoa(score))
	// 暂停关闭
	time.Sleep(time.Second * 10)
}

func main() {
	rand.Seed(time.Now().UnixNano()) // 创建随机数种子
	Clib.HideCursor()                // 隐藏控制台光标
	MapInit()                        // 初始化地图
	var s Snake                      // 创建蛇对象
	s.SnakeInit()                    // 初始化蛇信息
	s.randomFood()                   // 随机食物
	s.Play()                         // 开始游戏
	s.gameOver()                     // 游戏结束
}
